package com.hyacinth.server.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类加载工具类
 * @author zhangfb
 * @version 1.0.0.1
 * @since JDK 1.8
 */
public class ClassUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtils.class);

    private static final String CLASS_SUFFIX = ".class";
    private static final String PROTOCOL_FILE = "file";
    private static final String PROTOCOL_JAR = "jar";

    /**
     * 加载包下面的所有类集合
     * @param packageName 包名
     * @param set 类集合
     */
    public static void loadClassesByPackName(String packageName, Set<Class<?>> set) {

        String packageDirName = resourceName(packageName);

        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if (PROTOCOL_FILE.equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(packageName, filePath, set);
                } else if (PROTOCOL_JAR.equals(protocol)) {
                    JarFile jar;
                    try {
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        Enumeration<JarEntry> entries = jar.entries();
                        while (entries.hasMoreElements()) {
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            if (name.charAt(0) == '/') {
                                name = name.substring(1);
                            }
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                if (idx != -1) {
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                if (name.endsWith(CLASS_SUFFIX) && !entry.isDirectory()) {
                                    String className = name.substring(packageName.length() + 1, name.length() - 6);
                                    try {
                                        set.add(Class.forName(packageName + '.' + className));
                                    } catch (ClassNotFoundException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        LOGGER.error("IOException", e);
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("IOException", e);
        }
    }

    /**
     * 加载包下面的所有文件
     * @param packageName 包名
     * @param packagePath 保路径
     * @param classes 文件集合
     */
    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] files = dir.listFiles(file -> file.isDirectory() || file.getName().endsWith(CLASS_SUFFIX));
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
                } else {
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    try {
                        classes.add(Class.forName(packageName + '.' + className));
                    } catch (ClassNotFoundException e) {
                        LOGGER.error("ClassNotFoundException", e);
                    }
                }
            }
        }
    }

    /**
     * 包名转换为资源名
     * @param packName 包名 com.hyacinth.dubbo.io
     * @return 资源名
     */
    private static String resourceName(String packName) {
        if (packName != null) {
            String resourceName = packName.replace(".", "\\");
            if (resourceName.startsWith("/")) {
                resourceName = resourceName.substring(1);
            }
            return resourceName;
        }
        return null;
    }

    /**
     * 获取默认类加载器
     * @return ClassLoader
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable ex) {
            LOGGER.debug("Cannot access thread context ClassLoader - falling back...");
        }
        if (cl == null) {
            cl = ClassUtils.class.getClassLoader();
            if (cl == null) {
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable ex) {
                    LOGGER.debug("Cannot access system ClassLoader - oh well, maybe the caller can live with null...");
                }
            }
        }
        return cl;
    }

    /**
     * 获取某个类的方法
     * @param clazz the clazz to analyze
     * @param methodName the name of the method
     * @param paramTypes the parameter types of the method
     * @return the method, or {@code null} if not found
     * @see Class#getMethod
     */
    public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
        Assert.notNull(clazz, "类不能为空");
        Assert.notNull(methodName, "方法签名不能为空");
        Method method = null;
        if (paramTypes != null) {
            try {
                method = clazz.getMethod(methodName, paramTypes);
            } catch (NoSuchMethodException ex) {
                LOGGER.error("Expected method not found", ex);
            }
        } else {
            Set<Method> candidates = new HashSet<>(1);
            Method[] methods = clazz.getMethods();
            for (Method m : methods) {
                if (methodName.equals(m.getName())) {
                    candidates.add(m);
                }
            }
            if (candidates.size() == 1) {
                method =  candidates.iterator().next();
            } else if (candidates.isEmpty()) {
                LOGGER.error("Expected method not found: " + clazz.getName() + '.' + methodName);
            } else {
                LOGGER.error("No unique method found: " + clazz.getName() + '.' + methodName);
            }
        }
        return method;
    }

}
